import { JKManager } from "../JKManager"
import { FloatViewBase } from "./FloatViewBase"
import { NormalViewBase } from "./NormalViewBase"
import { PopupViewBase } from "./PopupViewBase"
import { TipViewBase } from "./TipViewBase"

export let ActionInTime = 0.3, // 界面执行进入动画时间，秒
    ActionOutTime = 0.2, // 界面执行离开动画时间，秒
    ActionInHoldTime = 2 // 提示界面自动关闭等待时间，秒

export type ActionInType =
    `ScaleIn` | // 缩放进入
    `MoveInFromTop` | // 从上平移进入
    `MoveInFromBottom` | // 从下平移进入
    `MoveInFromLeft` | // 从左平移进入
    `MoveInFromRight` | // 从右平移进入
    `FadeIn` // 淡入
export type ActionOutType =
    `ScaleOut` | // 缩放离开
    `MoveOutToTop` | // 从上平移离开
    `MoveOutToBottom` | // 从下平移离开
    `MoveOutToLeft` | // 从左平移离开
    `MoveOutToRight` | // 从右平移离开
    `FadeOut` // 淡出

type NodeView = cc.Node & {
    actionInType?: ActionInType,
    actionIn?: cc.Tween,
}
type ScriptView = cc.Component & {
    getViewActionTargetNode: () => cc.Node,
}

/**
 * 界面管理单例
 */
export class UiManager {
    private _ndUiRoot: cc.Node
    private _ndNormal: cc.Node
    private _ndFloat: cc.Node
    private _ndPopup: cc.Node
    private _ndTip: cc.Node
    private _ndWaitMask: cc.Node
    private _ndTransparentMask: cc.Node
    private _normalViewPaths: string[]
    private _normalViewMap: { [key: string]: cc.Node }
    private _floatViewMap: { [key: string]: cc.Node }
    private _popupInfosList: { path: string, data?: unknown, actionInType?: ActionInType, }[]
    private _curPopupInfo: { path: string, data?: unknown, actionInType?: ActionInType, }
    private _popupViewMap: { [key: string]: cc.Node }
    private _tipInfosList: { path: string, data?: unknown, actionInType?: ActionInType, }[]
    private _curTipInfo: { path: string, data?: unknown, actionInType?: ActionInType, }
    private _tipViewMap: { [key: string]: cc.Node }

    init(ndUiRoot: cc.Node): void {
        cc.log(`${cc.js.getClassName(this)} init`)

        this._ndUiRoot = ndUiRoot
        this._ndNormal = cc.find(`ndNormal`, this._ndUiRoot)
        this._ndFloat = cc.find(`ndFloat`, this._ndUiRoot)
        this._ndPopup = cc.find(`ndPopup`, this._ndUiRoot)
        this._ndTip = cc.find(`ndTip`, this._ndUiRoot)
        this._ndWaitMask = cc.find(`ndWaitMask`, this._ndUiRoot)
        this._ndTransparentMask = cc.find(`ndTransparentMask`, this._ndUiRoot)

        this._normalViewPaths = []
        this._normalViewMap = cc.js.createMap()
        this._floatViewMap = cc.js.createMap()
        this._popupInfosList = []
        this._popupViewMap = cc.js.createMap()
        this._tipInfosList = []
        this._tipViewMap = cc.js.createMap()
        this._ndWaitMask.active = false
        this._ndTransparentMask.active = false
    }

    //==============================================================================================================================
    //========================================================= 内部方法 ============================================================
    //==============================================================================================================================

    /**
     * 打开一个新的普通界面，并关闭上一个普通界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionType 动画类型
     * @param record 是否需要记录
     * @param call 回调，onLoad之前同步调用
     */
    private _showNormalView(path: string, data: unknown, actionType: ActionInType | ActionOutType = null, record: boolean, call: (ndView: cc.Node) => void = null): void {
        cc.log(`${cc.js.getClassName(this)} _showNormalView path = ${path} data = `, data)

        this._normalViewPaths.push(path)
        cc.resources.load(path, (err, prefab: cc.Prefab) => {
            if (err) {
                cc.error(err)
                this._normalViewPaths.pop()
            } else {
                let normalView: NodeView = this._normalViewMap[path] || cc.instantiate(prefab),
                    scView: ScriptView & NormalViewBase = normalView.getComponent(normalView.name),
                    curNdView = this._ndNormal.children[this._ndNormal.children.length - 1],
                    curScView: ScriptView & NormalViewBase = curNdView && curNdView.getComponent(curNdView.name),
                    afterIn = () => {
                        if (record) {
                            if (curNdView) {
                                curNdView.stopAllActions()
                                curScView.clear()
                                curNdView.active = false
                            }
                        }
                    },
                    beforeAction = () => {
                        call && call(normalView)
                        if (this._normalViewMap[path]) {
                            normalView.setSiblingIndex(this._ndNormal.children.length - 1)
                            normalView.active = true
                            scView.refreshFloatView()
                            scView.init(data, false)
                        } else {
                            this._ndNormal.addChild(normalView)
                            this._normalViewMap[path] = normalView
                            scView.init(data, true)
                        }
                    }
                switch (actionType) {
                    case `ScaleIn`: // 缩放进入
                    case `MoveInFromTop`: // 从上平移进入
                    case `MoveInFromBottom`: // 从下平移进入
                    case `MoveInFromLeft`: // 从左平移进入
                    case `MoveInFromRight`: // 从右平移进入
                    case `MoveInFromRight`: // 从右平移进入
                    case `FadeIn`: // 淡入
                        beforeAction()
                        this._doActioonIn(scView, () => {
                            afterIn()
                        }, actionType)

                        break
                    case `ScaleOut`: // 缩放离开
                    case `MoveOutToTop`: // 从上平移离开
                    case `MoveOutToBottom`: // 从下平移离开
                    case `MoveOutToLeft`: // 从左平移离开
                    case `MoveOutToRight`: // 从右平移离开
                    case `FadeOut`: // 淡出
                        beforeAction()
                        normalView.setSiblingIndex(this._ndNormal.children.length - 2)
                        curScView && this._doActionOut(curScView, () => {
                            afterIn()
                        }, actionType)

                        break

                    default:
                        normalView.scale = 1
                        if (curNdView) {
                            normalView.scale = 1
                        }
                        beforeAction()
                        afterIn()
                        this.hideTransparentMask()

                        break
                }
            }
        })
    }

    /**
     * 打开一个悬浮窗界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionInType 动画类型
     * @param call 回调，onLoad之前同步调用
     */
    private _showFloatView(path: string, data: unknown, actionInType: ActionInType = null, call: (ndView: cc.Node) => void): void {
        cc.log(`${cc.js.getClassName(this)} _showFloatView path = ${path} data = `, data)

        cc.resources.load(path, (err, prefab: cc.Prefab) => {
            if (err) {
                cc.error(err)
            } else {
                let floatView: NodeView = this._floatViewMap[path] || cc.instantiate(prefab),
                    scView: ScriptView & FloatViewBase = floatView.getComponent(floatView.name)
                call && call(floatView)
                if (this._floatViewMap[path]) {
                    floatView.setSiblingIndex(this._ndFloat.children.length - 1)
                    floatView.active = true
                    scView.init(data, false)
                } else {
                    this._floatViewMap[path] = floatView
                    this._ndFloat.addChild(floatView)
                    scView.init(data, true)
                }
                switch (actionInType) {
                    case `ScaleIn`: // 缩放进入
                    case `MoveInFromTop`: // 从上平移进入
                    case `MoveInFromBottom`: // 从下平移进入
                    case `MoveInFromLeft`: // 从左平移进入
                    case `MoveInFromRight`: // 从右平移进入
                    case `FadeIn`: // 淡入
                        this._doActioonIn(scView, () => {
                        }, actionInType)

                        break

                    default:

                        break
                }
            }
        })
    }

    /**
     * 打开一个弹窗界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionInType 动画类型
     * @param call 回调，onLoad之前同步调用
     */
    private _showPopupView(path: string, data: unknown, actionInType: ActionInType = null, call: (ndView: cc.Node) => void = null): void {
        cc.log(`${cc.js.getClassName(this)} _showPopupView path = ${path} data = `, data)

        cc.resources.load(path, (err, prefab: cc.Prefab) => {
            if (err) {
                cc.error(err)
            } else {
                let popupView = this._popupViewMap[path] || cc.instantiate(prefab),
                    scView: ScriptView & PopupViewBase = popupView.getComponent(popupView.name)
                call && call(popupView)
                if (this._popupViewMap[path]) {
                    popupView.active = true
                    scView.init(data, false)
                } else {
                    this._popupViewMap[path] = popupView
                    this._ndPopup.addChild(popupView)
                    scView.init(data, true)
                }
                switch (actionInType) {
                    case `ScaleIn`: // 缩放进入
                    case `MoveInFromTop`: // 从上平移进入
                    case `MoveInFromBottom`: // 从下平移进入
                    case `MoveInFromLeft`: // 从左平移进入
                    case `MoveInFromRight`: // 从右平移进入
                    case `FadeIn`: // 淡入
                        this._doActioonIn(scView, () => {
                        }, actionInType)

                        break

                    default:

                        break
                }
            }
        })
    }

    /**
     * 打开一个弹窗界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionInType 动画类型
     * @param call 回调，onLoad之前同步调用
     */
    private _showTipView(path: string, data: unknown, actionInType: ActionInType = null, call: (ndView: cc.Node) => void = null): void {
        cc.log(`${cc.js.getClassName(this)} _showTipView path = ${path} data = `, data)

        cc.resources.load(path, (err, prefab: cc.Prefab) => {
            if (err) {
                cc.error(err)
            } else {
                let tipView = this._tipViewMap[path] || cc.instantiate(prefab),
                    scView: ScriptView & TipViewBase = tipView.getComponent(tipView.name),
                    actionOutType: ActionOutType = null
                call && call(tipView)
                if (this._tipViewMap[path]) {
                    tipView.active = true
                    scView.init(data, false)
                } else {
                    this._tipViewMap[path] = tipView
                    this._ndTip.addChild(tipView)
                    scView.init(data, true)
                }
                switch (actionInType) {
                    case `ScaleIn`: // 缩放进入
                        actionOutType = `ScaleOut`

                        break
                    case `MoveInFromTop`: // 从上平移进入
                        actionOutType = `MoveOutToTop`

                        break
                    case `MoveInFromBottom`: // 从下平移进入
                        actionOutType = `MoveOutToBottom`

                        break
                    case `MoveInFromLeft`: // 从左平移进入
                        actionOutType = `MoveOutToLeft`

                        break
                    case `MoveInFromRight`: // 从右平移进入
                        actionOutType = `MoveOutToRight`

                        break
                    case `FadeIn`: // 淡入
                        actionOutType = `FadeOut`

                        break

                    default:

                        break
                }
                this._doActioonIn(scView, () => {
                    setTimeout(() => {
                        tipView.activeInHierarchy && this.closeTipView(actionOutType)
                    }, ActionInHoldTime * 1000);
                }, actionInType)
            }
        })
    }

    /**
     * 对指定界面执行进入动画
     * @param scView 界面脚本
     * @param call 回调
     * @param actionInType 动画类型
     */
    _doActioonIn(scView: ScriptView, call: () => void = null, actionInType: ActionInType = null) {
        cc.log(`${cc.js.getClassName(this)} _doActioonIn scView = `, scView, ` actionInType = ${actionInType}`)

        let ndView: NodeView = scView.getViewActionTargetNode() || scView.node
        if (ndView.activeInHierarchy) {
            ndView.actionInType = actionInType
            let widget = ndView.getComponent(cc.Widget),
                widgetEnabled = widget && widget.enabled
            if (widget) {
                widgetEnabled && widget.updateAlignment()
                widget.enabled = false
            }
            let tarY = ndView.y,
                tarX = ndView.x,
                onActionDone = () => {
                    this.hideTransparentMask()
                    if (widget) {
                        widget.enabled = widgetEnabled
                    }
                    call && call()
                }
            switch (actionInType) {
                case `ScaleIn`:
                    // 缩放
                    this.showTransparentMask()
                    ndView.scale = 0
                    cc.tween(ndView)
                        .to(ActionInTime, { scale: 1 }, cc.easeBackOut())
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveInFromTop`:
                    // 从上平移进入
                    this.showTransparentMask()
                    ndView.y = tarY + cc.Canvas.instance.node.height
                    cc.tween(ndView)
                        .to(ActionInTime, { y: tarY }, /*cc.easeBackOut()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveInFromBottom`:
                    // 从下平移进入
                    this.showTransparentMask()
                    ndView.y = tarY - cc.Canvas.instance.node.height
                    cc.tween(ndView)
                        .to(ActionInTime, { y: tarY }, /*cc.easeBackOut()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveInFromLeft`:
                    // 从左平移进入
                    this.showTransparentMask()
                    ndView.x = tarX - cc.Canvas.instance.node.width
                    cc.tween(ndView)
                        .to(ActionInTime, { x: tarX }, /*cc.easeBackOut()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveInFromRight`:
                    // 从右平移进入
                    this.showTransparentMask()
                    ndView.x = tarX + cc.Canvas.instance.node.width
                    cc.tween(ndView)
                        .to(ActionInTime, { x: tarX }, /*cc.easeBackOut()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `FadeIn`:
                    // 淡入
                    this.showTransparentMask()
                    ndView.opacity = 0
                    cc.tween(ndView)
                        .to(ActionInTime, { opacity: 255 }, /*cc.easeBackOut()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break

                default:
                    break
            }
        }
    }

    /**
     * 对指定界面执行离开动画
     * @param scView 界面脚本
     * @param call 回调
     * @param actionOutType 动画类型
     */
    _doActionOut(scView: ScriptView, call: () => void = null, actionOutType: ActionOutType = null) {
        cc.log(`${cc.js.getClassName(this)} _doActionOut scView = `, scView, ` actionOutType = ${actionOutType}`)

        let ndView: NodeView = scView.getViewActionTargetNode() || scView.node,
            actionIn = ndView.actionIn
        if (ndView.activeInHierarchy) {
            if (!actionOutType) {
                switch (ndView.actionInType) {
                    case `ScaleIn`:
                        // 缩放进入
                        actionOutType = `ScaleOut`

                        break
                    case `MoveInFromTop`:
                        // 从上平移进入
                        actionOutType = `MoveOutToTop`

                        break
                    case `MoveInFromBottom`:
                        // 从下平移进入
                        actionOutType = `MoveOutToBottom`

                        break
                    case `MoveInFromLeft`:
                        // 从左平移进入
                        actionOutType = `MoveOutToLeft`

                        break
                    case `MoveInFromRight`:
                        // 从右平移进入
                        actionOutType = `MoveOutToRight`

                        break
                    case `FadeIn`:
                        // 淡入
                        actionOutType = `MoveOutToRight`

                        break

                    default:
                        break
                }
            }
            let tarY: number,
                tarX: number,
                onActionDone = () => {
                    this.hideTransparentMask()
                    call && call()
                }
            switch (actionOutType) {
                case `ScaleOut`:
                    // 缩放离开
                    this.showTransparentMask()
                    cc.tween(ndView)
                        .to(ActionOutTime, { scale: 0 }, cc.easeBackIn())
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveOutToTop`:
                    // 从上平移离开
                    this.showTransparentMask()
                    tarY = ndView.y + cc.Canvas.instance.node.height
                    cc.tween(ndView)
                        .to(ActionOutTime, { y: tarY }, /*cc.easeBackIn()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveOutToBottom`:
                    // 从下平移离开
                    this.showTransparentMask()
                    tarY = ndView.y - cc.Canvas.instance.node.height
                    cc.tween(ndView)
                        .to(ActionOutTime, { y: tarY }, /*cc.easeBackIn()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveOutToLeft`:
                    // 从左平移离开
                    this.showTransparentMask()
                    tarX = ndView.x - cc.Canvas.instance.node.width
                    cc.tween(ndView)
                        .to(ActionOutTime, { x: tarX }, /*cc.easeBackIn()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `MoveOutToRight`:
                    // 从右平移离开
                    this.showTransparentMask()
                    tarX = ndView.x + cc.Canvas.instance.node.width
                    cc.tween(ndView)
                        .to(ActionOutTime, { x: tarX }, /*cc.easeBackIn()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break
                case `FadeOut`:
                    // 淡出
                    this.showTransparentMask()
                    ndView.opacity = 255
                    cc.tween(ndView)
                        .to(ActionOutTime, { opacity: 0 }, /*cc.easeBackIn()*/)
                        .call(() => {
                            onActionDone()
                        })
                        .start()

                    break

                default:
                    call && call()

                    break
            }
        }
    }

    //==============================================================================================================================
    //========================================================= 外部方法 ============================================================
    //==============================================================================================================================

    /**
     * 打开一个新的普通界面，并关闭上一个普通界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionInType 进入动画类型
     * @param record 是否需要记录，以便调用backNormalView时，可以根据记录一次返回，默认为true，如果不记录则需自行销毁界面
     * @param call 回调，onLoad之前同步调用
     * @param cashed 是否使用缓存，默认是
     */
    showNormalView(path: string, data: unknown = null, actionInType: ActionInType | ActionOutType = null, record = true, call: (ndView: cc.Node) => void = null, cashed: boolean = true): void {
        cc.log(`${cc.js.getClassName(this)} showNormalView path = ${path} data = `, data)

        if (!cashed) {
            this._normalViewMap[path] && this._normalViewMap[path].destroy()
            delete this._normalViewMap[path]
        }
        if (record) {
            if (this._normalViewPaths[this._normalViewPaths.length - 1] !== path) {
                this._showNormalView(path, data, actionInType, true, call)
            } else {
                cc.error(`The normalView "${path}" is already showed!`)
            }
        } else {
            this._showNormalView(path, data, actionInType, false, call)
        }
    }

    /**
     * 返回上一个普通界面，并关闭当前普通界面
     * @param data 数据
     * @param actionOutType 离开动画类型
     * @param call 回调，onLoad之前调用，(cc.Component scView 界面脚本)
     */
    backNormalView(data: unknown = null, actionOutType: ActionOutType = null, call: (ndView: cc.Node) => void = null): void {
        cc.log(`${cc.js.getClassName(this)} backNormalView data = `, data)

        if (this._normalViewPaths.length > 1) {
            this._normalViewPaths.pop()
            let path = this._normalViewPaths.pop()
            this._showNormalView(path, data, actionOutType, true, call)
        } else {
            cc.error(`There is no normalView recorded can go back!`)
        }
    }

    /**
     * 获取当前打开的可记录界面路径
     */
    getCurNormalViewPath(): string {
        return this._normalViewPaths[this._normalViewPaths.length - 1] || ``
    }

    /**
     * 获取前一个打开的可记录界面路径
     */
    getLastNormalViewPath(): string {
        return this._normalViewPaths[this._normalViewPaths.length - 2] || ``
    }

    /**
     * 打开一个悬浮窗界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionInType 进入动画类型
     * @param call 回调，onLoad之前同步调用
     */
    showFloatView(path: string, data: unknown, actionInType: ActionInType = null, call: (ndView: cc.Node) => void = null): void {
        cc.log(`${cc.js.getClassName(this)} showFloatView path = ${path} data = `, data)

        let exist = this._floatViewMap[path] && this._floatViewMap[path].active
        !exist && this._showFloatView(path, data, actionInType, call)
    }

    /**
     * 关闭一个悬浮窗界面
     * @param pathOrNodeOrScript 界面预制体资源路径或者界面节点或者界面脚本
     * @param actionOutType 离开动画类型
     */
    closeFloatView(pathOrNodeOrScript: string | cc.Node | (ScriptView & FloatViewBase), actionOutType: ActionOutType = null): void {
        cc.log(`${cc.js.getClassName(this)} closeFloatView pathOrNodeOrScript = `, pathOrNodeOrScript)

        let floatView: NodeView,
            path: string
        if (typeof pathOrNodeOrScript === `string`) {
            path = pathOrNodeOrScript
            floatView = this._floatViewMap[path]
        } else {
            if (pathOrNodeOrScript instanceof cc.Node) {
                floatView = pathOrNodeOrScript
            } else {
                floatView = pathOrNodeOrScript.node
            }
            for (const key in this._floatViewMap) {
                if (Object.prototype.hasOwnProperty.call(this._floatViewMap, key)) {
                    if (this._floatViewMap[key] === floatView) {
                        path = key

                        break
                    }
                }
            }
        }
        if (floatView && floatView.activeInHierarchy) {
            let scView: ScriptView & NormalViewBase = floatView.getComponent(floatView.name)
            this._doActionOut(scView, () => {
                if (floatView) {
                    scView.clear()
                    floatView.active = false
                }
            }, actionOutType)
        }
    }

    /**
     * 清空所有悬浮窗界面
     */
    clearFloatView() {
        cc.log(`${cc.js.getClassName(this)} clearFloatView`)

        this._ndFloat.removeAllChildren()
        this._floatViewMap = cc.js.createMap()
    }

    /**
     * 获取当前打开的悬浮窗界面路径
     */
    getFloatViewPaths(): string[] {
        cc.log(`${cc.js.getClassName(this)} getFloatViewPaths`)

        return Object.keys(this._floatViewMap)
    }

    /**
     * 根据界面预制体资源路径取当前打开的悬浮窗界面
     * @param path 界面预制体资源路径
     */
    getFloatView(path: string): cc.Node {
        cc.log(`${cc.js.getClassName(this)} getFloatView`)

        return this._floatViewMap[path]
    }

    /**
     * 打开一个弹窗界面
     * @param path 界面预制体资源路径
     * @param data 数据
     * @param actionInType 进入动画类型
     * @param unshift 是否队列前插入，默认否
     * @param call 回调，onLoad之前同步调用
     */
    showPopupView(path: string, data: unknown, actionInType: ActionInType = null, unshift: boolean = false, call: (ndView: cc.Node) => void = null): void {
        data = data || cc.js.createMap()
        cc.log(`${cc.js.getClassName(this)} showPopupView path = ${path} data = `, data)

        if (!this._curPopupInfo || (!JKManager.Utils.isEqualObj(data, this._curPopupInfo.data) && path !== this._curPopupInfo.path)) {
            if (unshift) {
                this._popupInfosList.unshift({
                    path: path,
                    data: data,
                    actionInType: actionInType,
                })
            } else {
                this._popupInfosList.push({
                    path: path,
                    data: data,
                    actionInType: actionInType,
                })
            }
            if (!this._curPopupInfo && this._popupInfosList.length > 0) {
                this._curPopupInfo = this._popupInfosList.shift()
                this._showPopupView(this._curPopupInfo.path, this._curPopupInfo.data, this._curPopupInfo.actionInType, call)
            }
        }
    }

    /**
     * 关闭弹窗界面
     * @param actionOutType 离开动画类型
     */
    closePopupView(actionOutType: ActionOutType = null): void {
        cc.log(`${cc.js.getClassName(this)} closePopupView`)

        let popupView: NodeView
        this._ndPopup.children.forEach(ndView => {
            if (ndView.active) {
                popupView = ndView

                return
            }
        })
        if (popupView && popupView.activeInHierarchy) {
            let scView: ScriptView & NormalViewBase = popupView.getComponent(popupView.name)
            this._doActionOut(scView, () => {
                if (popupView) {
                    scView.clear()
                    popupView.active = false
                    this.onRemovePopup()
                }
            }, actionOutType)
        }
    }

    /**
     * 弹窗界面关闭回调
     */
    onRemovePopup(): void {
        cc.log(`${cc.js.getClassName(this)} onRemovePopup`)

        this._curPopupInfo = null
        if (this._popupInfosList.length > 0) {
            this._curPopupInfo = this._popupInfosList.shift()
            this._showPopupView(this._curPopupInfo.path, this._curPopupInfo.data)
        }
    }

    /**
     * 清空所有弹窗界面
     */
    clearPopupView() {
        cc.log(`${cc.js.getClassName(this)} clearPopupView`)

        this._ndPopup.removeAllChildren()
        this._curPopupInfo = null
        this._popupInfosList = []
    }

    /**
     * 打开一个公共提示界面
     * @param msg 信息
     * @param actionInType 进入动画类型
     * @param callConfirm 确认回调
     * @param callCancel 取消回调
     */
    showCommonDialogView(msg: string, actionInType: ActionInType = null, callConfirm: () => void = null, callCancel: () => void = null): void {
        this.showPopupView(`JKCore/Prefab/CommonDialogView/CommonDialogView`, {
            msg: msg,
            callConfirm: callConfirm,
            callCancel: callCancel,
        }, actionInType)
    }

    /**
     * 打开一个提示界面
     * @param path 界面预制体资源路径
     * @param data 数据 {String msg}
     * @param actionInType 进入动画类型
     * @param unshift 是否队列前插入，默认否
     * @param call 回调，onLoad之前同步调用
     */
    showTipView(path: string, data: unknown, actionInType: ActionInType = null, unshift: boolean = false, call: (ndView: cc.Node) => void = null): void {
        data = data || cc.js.createMap()
        cc.log(`${cc.js.getClassName(this)} showTipView path = ${path} data = `, data)

        if (!this._curTipInfo || (!JKManager.Utils.isEqualObj(data, this._curTipInfo.data) && path !== this._curTipInfo.path)) {
            if (unshift) {
                this._tipInfosList.unshift({
                    path: path,
                    data: data || cc.js.createMap(),
                } || cc.js.createMap())
            } else {
                this._tipInfosList.push({
                    path: path,
                    data: data || cc.js.createMap(),
                } || cc.js.createMap())
            }
            if (!this._curTipInfo && this._tipInfosList.length > 0) {
                this._curTipInfo = this._tipInfosList.shift()
                this._showTipView(this._curTipInfo.path, this._curTipInfo.data, actionInType, call)
            }
        }
    }

    /**
     * 关闭提示界面
     * @param actionOutType 离开动画类型
     */
    closeTipView(actionOutType: ActionOutType = null): void {
        cc.log(`${cc.js.getClassName(this)} closeTipView`)

        let tipView: NodeView
        this._ndTip.children.forEach(ndView => {
            if (ndView.active) {
                tipView = ndView

                return
            }
        })
        if (tipView && tipView.activeInHierarchy) {
            let scView: ScriptView & NormalViewBase = tipView.getComponent(tipView.name)
            this._doActionOut(scView, () => {
                if (tipView) {
                    scView.clear()
                    tipView.active = false
                    this.onRemoveTip()
                }
            }, actionOutType)
        }
    }

    /**
     * 提示界面关闭回调
     */
    onRemoveTip(): void {
        cc.log(`${cc.js.getClassName(this)} onRemoveTip`)

        this._curTipInfo = null
        if (this._tipInfosList.length > 0) {
            this._curTipInfo = this._tipInfosList.shift()
            this._showTipView(this._curTipInfo.path, this._curTipInfo.data)
        }
    }

    /**
     * 清空所有提示界面
     */
    clearTipView() {
        cc.log(`${cc.js.getClassName(this)} clearPopupView`)

        this._ndPopup.removeAllChildren()

        this._curPopupInfo = null
        this._popupInfosList = []
    }

    /**
     * 打开一个公共提示界面，2秒后自动关闭
     * @param msg 信息
     * @param actionInType 进入动画类型
     */
    showCommonTipView(msg: string, actionInType: ActionInType = null): void {
        this.showTipView(`JKCore/Prefab/CommonTipView/CommonTipView`, {
            msg: msg,
        }, actionInType)
    }

    /**
     * 显示通用加载等待界面
     */
    showLoadingMask(): void {
        cc.log(`${cc.js.getClassName(this)} showLoadingMask`)

        this._ndWaitMask.active = true
    }

    /**
     * 隐藏通用加载等待界面
     */
    hideLoadingMask(): void {
        cc.log(`${cc.js.getClassName(this)} hideLoadingMask`)

        this._ndWaitMask.active = true
    }

    /**
     * 显示通用透明遮罩界面
     */
    showTransparentMask(): void {
        cc.log(`${cc.js.getClassName(this)} showTransparentMask`)

        this._ndTransparentMask.active = true
    }

    /**
     * 隐藏通用透明遮罩界面
     */
    hideTransparentMask(): void {
        cc.log(`${cc.js.getClassName(this)} hideTransparentMask`)

        this._ndTransparentMask.active = false
    }
}